"""
File:models.py
Author: anning
Date:-03-30
Connect:792910452@qq.com
Description:
设计数据库模型：
  1）用户信息:User
  2）用户角色信息：Role
  3）用户角色：用户=1：n ,一对多关系，外键写在多的一端

添加UserMixin后在终端中执行python manage.py shell
在交互环境下查看user具有哪些属性
users=User.query.all()
users
user=users[0]
dir(user) 查看用户具有哪些属性和方法
user.username  查看用户的username属性信息
这些方法在写逻辑代码时使用
print(user.is_active)

关系的分析:一对多关系中，外键写在多的一端
1）Role:User=1:N
2) User:Todo=1:N
3) User:Category=1:N
4) Category:Todo=1:N
"""
from flask import current_app

from app import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
# Flask中一个Model子类就是数据库中的一个表。默认表名为类名的小写‘User.lower()==>user’
# 当我们传入明文密码时，通过函数@password.setter password（）自动生成哈希密码
from itsdangerous import TimedJSONWebSignatureSerializer
from datetime import datetime


class User(UserMixin,db.Model):
    """
    因为继承了UserMixin类,自动继承里面的属性和方法
    Flask-Login提供了一个UserMixin类，包含常用方法的默认实现，且能满足大多数需求
       1）is_authenticated 用户是否已经登录
       2）is_active 是否允许用户登录？False代表用户禁用
       3）is_anonymous 是否匿名用户
       4）get_id() 返回用户的唯一标识符
    """
    __tablename__='users' #自定义数据表的表名
    id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    username=db.Column(db.String(100),unique=True,nullable=False)
    password_hash=db.Column(db.String(200),nullable=True)
    email=db.Column(db.String(50))
    phone=db.Column(db.String(20))
    # 存放用户是否确认登录，默认情况下用户没有确认
    confirmed=db.Column(db.Boolean,default=False)
    name=db.Column(db.String(64))       # 用户真实姓名
    location=db.Column(db.String(64))   # 所在地
    about_me=db.Column(db.Text())       # 自我介绍
    # 注册日期
    #     default 参数可以接受函数作为默认值
    #     所以每次生成默认值时，db.Column()都会调用指定的函数
    # utcnow为国际通用的当前时间
    create_date=db.Column(db.DateTime(),default=datetime.utcnow)
    # 最后一次访问时间,由于每次用户访问网络后，这个之都会被刷新，因此添加一个方法完成刷新，即def ping(self)
    last_seen=db.Column(db.DateTime(),default=datetime.utcnow)
    # 外键写在多的一端，外键关联的是roles表中的id列
    role_id=db.Column(db.Integer,db.ForeignKey('roles.id'))
    # 反向引用Todo:1)User添加属性todos 2)Todo添加属性user
    todos=db.relationship('Todo',backref='user')
    # 反向引用Category 1)User添加属性categories 2）Category添加属性user
    categories=db.relationship('Category',backref='user')

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self,password):
        # generate_password_hash(password,method=pbkd2:sha1,salt_length=8):密码加密的散列值，为密码进行哈希加密
        self.password_hash=generate_password_hash(password)

    def verity_password(self,password):
        # check_password_hash(hash,password):密码散列值和用户输入的密码是否一致
        return check_password_hash(self.password_hash,password)

    def generate_confirmation_token(self,expiration=3600):
        """生成一个令牌，有效期为一小时"""
        s=TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'],expiration)
        return s.dumps({'confirm':self.id})

    def confirm(self,token):
        """
        http://127.0.0.1:8000/auth/confirm/djwerqwfrfbdeorifjerf
        点击这个链接时，将confirm后面的加密盐反向解析生成一个{‘confirm’:self.id}这样的字典
        从而确认激活的账户
        """
        s=TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
        try:
            data=s.loads(token)
            # 解密的信息是一个字典：{‘confirm’:1}
        except Exception as e:
            return False
        if data.get('confirm') != self.id:
            return False
        else:
            self.confirmed=True
            db.session.add(self)
            db.session.commit()
            return True
        # 当数据库发生改变时，就需要对数据库进行更新
        # 首先进行数据迁移，然后更新。python manage.py db migrate//python manage.py db upgrade

    def ping(self):
        """刷新用户的最后访问时间,在登录路由下，当用户登录成功后，使用ping方法更新最后一次登录时间"""
        self.last_seen=datetime.utcnow()
        db.session.add(self)
        db.session.commit()

    def __repr__(self):
        return '<User:%s>'% (self.username)


class Role(db.Model):
    __tablename__='roles'
    id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    name=db.Column(db.String(100),unique=True,nullable=False)
    # 做了两件事：1）Role添加属性users 2）User添加属性role
    users=db.relationship('User',backref='role')

    def __repr__(self):
        return '<Role:%s>'%(self.name)


class Todo(db.Model):
    __tablename__='todos'
    id=db.Column(db.Integer,autoincrement=True,primary_key=True)
    content=db.Column(db.String(100))  # 任务内容
    status=db.Column(db.Boolean,default=False)   # 任务状态
    add_time=db.Column(db.DateTime,default=datetime.utcnow) # 任务创建时间
    # 任务的类型，关联分类表的id，因为分类：任务=1：N，外键写在多的一端
    category_id=db.Column(db.Integer,db.ForeignKey('categories.id'))
    # 任务所属用户，用户：任务=1：N，因此外键写在任务一端，关联的是用户表的id
    user_id=db.Column(db.Integer,db.ForeignKey('users.id'))

    def __repr__(self):
        return '<Todo %s>'%(self.content[:6])


class Category(db.Model):
    __tablename__='categories'
    id=db.Column(db.Integer,autoincrement=True,primary_key=True)
    name=db.Column(db.String(20),unique=True)
    add_time=db.Column(db.DateTime,default=datetime.utcnow)  # 任务创建时间
    # 用户：分类=1：N
    user_id=db.Column(db.Integer,db.ForeignKey('users.id'))
    # 反向引用：1）Category添加todos属性 2）Todo添加categories属性
    todos=db.relationship('Todo',backref='category')


    def __repr__(self):
        return '<Category %s>'%(self.name)


# 加载用户的回调函数；如果能找到用户，返回用户对象；否则返回None
# 当有用户登录时，通过查询用户id，返回此用户id对应的用户信息

# login回调函数的作用：
# 注册回调函数 当没有session_id时，通过装饰器指定的函数来读取用户到session中
# 达到前端可通过current_user去获取当前登录的用户


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

